#include "sgu.h"

SGU_NS_BEGIN

#ifdef SGU_DEBUG
	#define BADSTORAGE(t) throws(t,__FILE__,__LINE__)
#else
	#define BADSTORAGE(t) throws(t)
#endif


//**** TIStorage

TIStorage::TIStorage(const ConstSegment & data) : _data(data)
{
	_data.addUser(this);
	_ptr = 0;
}

TIStorage::~TIStorage()
{
	_data.delUser(this);
	//if(_ptr != _data.Size()) THROWSGUFUNCEXCEPTION("/not eof");
}


char TIStorage::read()
{
	if(_ptr>=_data.size()) THROWSGUFUNCEXCEPTION("/eof");
	char k = qread();
	static const char * s1 = "([{<";
	static const char * s2 = ")]}>";
	const char * p1 = strchr(s1,k);
	const char * p2 = strchr(s2,k);
	ASSERT(!(p1&&p2));
	if(p1)
		_nesting.push_back(k);

	if(p2)
	{
		ASSERT(!_nesting.empty());
		ASSERT(_nesting.back()==s1[p2-s2]);
		_nesting.pop_back();
	}
	return k;
}

char TIStorage::qread()
{
	if(_ptr>=_data.size()) THROWSGUFUNCEXCEPTION("/eof");
	return _data.cptr()[_ptr++];
}

char TIStorage::qpeek()
{
	if(_ptr>=_data.size()) THROWSGUFUNCEXCEPTION("/eof");
	return _data.cptr()[_ptr];
}

void TIStorage::qskip(size_t n)
{
	_ptr += n;
	if(_ptr > _data.size()) THROWSGUFUNCEXCEPTION("/eof");
}

void TIStorage::qskip()
{
	++_ptr;
	if(_ptr > _data.size()) THROWSGUFUNCEXCEPTION("/eof");
}


const char * TIStorage::queryPtr() const
{
	return _data.cptr() + _ptr;
}

TIStorage & TIStorage::operator >> (Cmd op)
{
	switch(op)
	{
		case BEGIN : (*this) >> "{"; break;
		case END : (*this) >> "}"; break;
		case ABORT :
		{
			size_t s = _nesting.size();
			size_t j;
			for(j=s;j>0;--j) if(_nesting[j-1]=='{') break;
			while(_nesting.size()>j-1) read();
		}; break;
	}
	return *this;
}

TIStorage & TIStorage::operator >> (Serializable & o)
{
	o.serialize(*this);
	return *this;
}

TIStorage & TIStorage::operator >> (unsigned int & x)
{
	eatWhite();
	char * endptr;
	const char * ptr = queryPtr();
	x = stou(queryPtr(),&endptr);
	if(endptr==ptr) THROWSGUFUNCEXCEPTION("/bad");
	qskip(endptr-ptr);
	
	return *this;
}

TIStorage & TIStorage::operator >> (int & x)
{
	eatWhite();
	char * endptr;
	const char * ptr = queryPtr();
	x = strtol(queryPtr(),&endptr,10);
	if(endptr==ptr) THROWSGUFUNCEXCEPTION("/bad");
	qskip(endptr-ptr);
	
	return *this;
}


TIStorage & TIStorage::operator >> (double & x)
{
	eatWhite();
	char * endptr;
	const char * ptr = queryPtr();
	x = strtod(queryPtr(),&endptr);
	if(endptr==ptr) THROWSGUFUNCEXCEPTION("/bad");
	qskip(endptr-ptr);
	
	return *this;
}

TIStorage & TIStorage::operator >> (const char * s)
{
	eatWhite();
	while(*s)
	{
		if(read()!=*s) THROWSGUFUNCEXCEPTION("/bad");
		++s;
	}
	
	return *this;
}

TIStorage & TIStorage::operator >> (std::string & a)
{
	if(qpeek()!='\"') THROWSGUFUNCEXCEPTION("/bad");
	qskip();

	a.clear();
	while(true)
	{
		char k = qread();
		if(k=='\"') break;
		if(k!='\\') a += k;
		else
		{
			char m = qread();
			switch(m)
			{
				case '\'' :
				case '\"' :
				case '\\' :
					a += m; break;
 
				case 'r' : a += '\r'; break;
				case 'n' : a += '\n'; break;

				case 'x' :
				{
					char b0 = qread();
					if(!isxdigit(b0)) THROWSGUFUNCEXCEPTION("/bad");
					char b1 = qread();
					if(!isxdigit(b1)) THROWSGUFUNCEXCEPTION("/bad");
					char str[3] = {b0,b1,0};
					a += (char)strtol(str,0,16);
				}; break;
			}
		}
	}			
			
	return *this;
}


void TIStorage::eatWhite()
{
	while(strchr(" \t\r\n",qpeek())) qskip();
}

//**** TOStorage

TOStorage::TOStorage(DynamicSegment & data) : _data(data)
{
	_data.addUser(this);
	_ptr = 0;
	_needspaceafter = false;
	_newline = true;
	_error = false;
}

TOStorage::~TOStorage()
{
	if(!_newline) qwrite('\n');
	_data.resize(_ptr);
	_data.delUser(this);
	ASSERT(_radix.empty() || _error);
	ASSERT(_nesting.empty() || _error);
}

#ifdef SGU_DEBUG
void TOStorage::throws(const char * str, const char * file, int line)
{
	_error = true;
	throw SGUException(str,file,line);
}	
#else
void TOStorage::throws(const char * str)
{
	_error = true;
	throw SGUException(str);
}	
#endif

TOStorage & TOStorage::operator << (const Serializable & o)
{
	o.serialize(*this);
	return *this;
}

TOStorage & TOStorage::operator << (const char * s)
{
	write(s);
	return *this;
}

TOStorage & TOStorage::operator << (Cmd w)
{
	switch(w)
	{
		case POP   : ASSERT(!_radix.empty()); _radix.pop(); break;
		case NL    : if(!_newline) qwrite('\n'); _newline=true; _needspaceafter=false; break;
		case BLANK : (*this) << NL; qwrite('\n'); break;
		case BEGIN : (*this) << NL << "{" << NL; break;
		case END   : (*this) << NL << "}" << NL; break;

		default : ASSERT(false);
	}

	return *this;
}

TOStorage & TOStorage::operator << (Radix w)
{
	_radix.push(w);
	return *this;
}



TOStorage & TOStorage::operator << (int n)
{
	indent();
	if(_needspaceafter) qwrite(' ');
	qreserve(sizeof(int)*3+2); //3 for base10, 2 for sign and null
	qskip(itod(n,queryPtr()));
	_needspaceafter = true;
	_newline = false;
	return *this;
}

TOStorage & TOStorage::operator << (unsigned int n)
{
	if(_radix.empty()) BADSTORAGE("/radix empty");
	indent();
	if(_needspaceafter) qwrite(' ');
	qreserve(sizeof(int)*CHAR_BIT+1);
	char * a = queryPtr();
	size_t sz = 0;
	
	switch(_radix.top())
	{
		case DEC: sz = utod(n,a); break;
		case HEX: sz = utox((unsigned int)n,a); break;
		case BIN: sz = utob((unsigned int)n,a); break;
		default : ASSERT(false);
	}

	qskip(sz);
	_needspaceafter = true;
	_newline = false;

	return *this;
}




void TOStorage::write(char k)
{
	ASSERT(k!='\"');
	ASSERT(k>=32);
	//ASSERT(k<128);
	
	static const char * s1 = "([{<";
	static const char * s2 = ")]}>";
	static const char * s3 = "`~!@#$%^&*-=+;:,<.>/?|";
	const char * p1 = strchr(s1,k);
	const char * p2 = strchr(s2,k);
	const char * p3 = strchr(s3,k);
	ASSERT(!(p1&&p2));
	if(!(p1||p2))
	{
		indent();
		if(_needspaceafter && !p3) qwrite(' ');
		qwrite(k);
	}
	else
	{
		if(p1)
		{
			indent();
			qwrite(k);
			_nesting.push(k);
		}

		if(p2)
		{
			ASSERT(!_nesting.empty());
			ASSERT(_nesting.top()==s1[p2-s2]);
			_nesting.pop();
			indent();
			qwrite(k);
		}
	}
	_newline = false;
	_needspaceafter = false;
}
		
		

void TOStorage::write(const char * s)
{
	while(*s)
	{
		write(*s);
		++s;
	}
}

void TOStorage::qwrite(char k)
{
	_data.resize(_ptr+1);
	*(_data.ptr()+_ptr) = k;
	++_ptr;
}

void TOStorage::qwrite(const char * s)
{
	while(*s) qwrite(*(s++));
}

TOStorage & TOStorage::operator << (const std::string & a)
{
	indent();
	qwrite('\"');
	size_t sz = a.length();
	LOOP(size_t,n,sz)
	{
		char k = a.at(n);
		switch(k)
		{
			case '\'' : qwrite("\\\'"); break;
			case '\"' : qwrite("\\\""); break;
			case '\\' : qwrite("\\\\"); break;
			case '\r' : qwrite("\\r"); break;
			case '\n' : qwrite("\\n"); break;
			
			default :
			{
				if(k<32)
				{
					qwrite("\\x");
					char tmp[3];
					utobase((unsigned char)k,tmp,16,2);
					qwrite(tmp);
				}
				else qwrite(k);
			}
		}
	}
	qwrite('\"');
	_newline = false;

	return *this;
}

TOStorage & TOStorage::operator << (const std::wstring & a)
{
	indent();
	qwrite('\"');
	size_t sz = a.length();
	LOOP(size_t,n,sz)
	{
		wchar_t k = a.at(n);
		switch(k)
		{
			case L'\'' : qwrite("\\\'"); break;
			case L'\"' : qwrite("\\\""); break;
			case L'\\' : qwrite("\\\\"); break;
			case L'\r' : qwrite("\\r"); break;
			case L'\n' : qwrite("\\n"); break;
			
			default :
			{
				if(k<32 || k>=128)
				{
					qwrite("\\u");
					char tmp[sizeof(wchar_t)*2+1];
					utobase((unsigned short)k,tmp,16,sizeof(wchar_t)*2);
					qwrite(tmp);
				}
				else qwrite(k);
			}
		}

	}

	qwrite('\"');
	_newline = false;

	return *this;
}


TOStorage & TOStorage::operator << (double x)
{
	indent();
	if(_needspaceafter) qwrite(' ');
	qreserve(256);
	qskip(rtos(x,queryPtr()));
	_needspaceafter = true;
	return *this;
}


char * TOStorage::queryPtr() const
{
	return _data.ptr() + _ptr;
}

void TOStorage::qskip(size_t n)
{
	ASSERT(_ptr+n <= _data.size());
	_ptr += n;
}

void TOStorage::qreserve(size_t n)
{
	_data.resize(_ptr + n);
}

void TOStorage::indent()
{
	if(_newline) LOOP(size_t,j,_nesting.size()) qwrite(' ');
}

SGU_NS_END
